毫无疑问,Web 性能的终极目标是减少到用户端的延迟。让用户能够尽快的打开前端网页并进行相关交互。
就 HTTP 而言,理想的协议应该看起来是这样的:
而对于客户端来说,应该尽可能发送少的数据给服务器,从服务端下载尽可能少的数据,尽可能减少 Round Trips
客户端与服务器无论是哪一边,额外的数据流都会带来额外的延迟开销,与此同时也更容易出现拥塞和丢包问题,这无疑严重影响了性能。多余的 Round Trip 同样会增加延迟,尤其是在移动网络下(100ms 是让用户感觉到系统立即做出响应的时间上限)那么究竟理想中的 HTTP 是什么样的,而我们又该如何去优化?
HTTP/1.1
HTTP/1.1 这个协议本身有很多的优点,不幸的是性能并不在其中。一个典型的页面请求是这样的:
显然这并不理想
HTTP/1 是 chatty 类的协议 ,因为需要不断的去向服务器请求新东西,首先是 HTML 其次是 CSS 和 JavaScript,每一次的交换都增加了额外或者是更多的 Round Trip 延迟,明显和「尽可能减少 Round Trips」的目标背道而驰。其次,页面上的请求增加了很多的数据,违背了「应该尽可能发送少的数据给服务器」的原则,请求中增加了诸如 Referer、User-Agent 等啰嗦的首部,当然 Cookie 是每一次请求都要带上的。
最后因为 HTTP/1 的线头阻塞 (Head of line blocking) ,使得合并多个请求为一个请求成为公共实践,比如 CSS sprite、inline image 等其他系列的实践,看似华丽的 HTTP/1 性能优化,实则有一定的开销。它下载的数据远远超过客户端展示页面所需要的数据,违背了理想化的原则,意味着我们无法尽可能快速的展示页面 ,好在 HTTP/1.1 也不是一无是处,在性能方面,它提供了缓存。这就使得你可以重用新鲜的缓存,而无需重新发起额外的请求,当缓存中存在不新鲜的 copy 时,能很好的避免传输大量的数据。
HTTP/2
HTTP/2 试图从以下几个方面,解决 HTTP/1.1 中的几个历史遗留问题:
多路复用 (Multiplexing)
多路复用意味着线头阻塞将不在是一个问题,允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息,合并多个请求为一个的优化将不再适用。
首部压缩 (Header Compression)
首部压缩移除了请求中的一些啰嗦首部,你可以通过很少的 IP package,承载数十个乃至上百个的请求,更符合最小数据量的理想化原则。
服务端推送(Server Push)
服务器可以向客户端推送所需要的资源,避免不必要的 Round trips
所以一个典型的 HTTP/2 请求交互是这样的:
这里你会发现,服务端会向客户端发送 CSS,JavaScript 以及图片文件,而无需通知客户端,
服务端知道客户端可能会需要什么,所以它使用 Server Push 发送多个响应给客户端。节省了 Round trip
此时就变成了一个 less chatty 的协议,同时也充分的利用了网络资源。
友情提示,并不是说这些都已经是唾手可得很容易的,关于 HTTP/2 依旧是有许多的问题,尤其是何时去 Push,我会分段在后期进行解释
HTTP/2 缓存策略 + Cache Digests
关于 Server Push ,一个常见的问题是:
「如果客户端早已在缓存中有了一份 copy 怎么办?」
因为 Push 本身具有投机性,所以肯定会出现推送过去的东西浏览器不需要的情况。这种情况下,HTTP/2 允许客户端通过 RESET_STREAM 主动取消 Push ,然而这样的话,原本可以用于更好方向的 Push 就白白的浪费掉数据往返的价值。对此,一个推荐的解决方案是客户端使用一个简洁的 Cache Digest 来告诉服务器,哪些东西已经在缓存,至此服务器就会知道哪些是客户端所需要的。
因为 Cache Digest 使用的是 Golumb Compressed Sets,浏览器客户端可以通过一个连接发送一些少于 1K 字节的 packets 给服务端通知哪些是已经在缓存中存在的。
现在,我们已经解决了 chattiness、额外的 Round trip 开销、啰嗦首部的数据浪费,inline 以及过去的其他优化行为、最后则是 push 重复资源带来数据浪费,这和我们理想化的目标越来越近了。
Cache Digests 只是其中一个提案之一, 但在 HTTP 社区有着更多其他的解决方案,我们也希望在不久的将来看到他们的身影。
TCP
至此,我还没有谈到的其他协议对浏览器加载网页时的性能影响。在 HTTP 开始之前 TCP 需要进行三次握手,来协商新连接的参数。这意味着在建立连接的时候,一个最简单的 HTTP 请求也需要 2 个 round-trip time 才能完成
TCP Fast Open 允许应用在 TCP 握手期间(SYN 和 SYN+ACK packets)交换数据,这样可以减少一次 RTT,不幸的是目前只支持 Linux 以及 OSX,未来 TFO 在 HTTP 上将会有更多巧妙的运用。
TFO 不保证与 SYN 数据包发送的数据将只出现一次;这是容易重复(由于重传),甚至被恶意的重放攻击。因此,HTTP POST 并不是在一个 TFO 连接上建立第一个请求的一个好主意。并且,使用 GET 也依然会有很大的副作用,浏览器也没有好的办法来检测哪个 URL 可以做到这一点的。
TLS
在传输应用数据之前,客户端必须与服务端协商密钥、加密算法等信息,服务端还要把自己的证书发给客户端表明其身份,这些环节构成 TLS 握手过程,还没把客户端和服务端处理时间算进去。光是 TLS 握手就需要消耗两个 RTT(Round-Trip Time,往返时间)
而 session tickets 可以将 TLS 握手所需 RTT 减少到 1 个:
不久的未来,TLS 1.3 将会支持 Zero Round Trip 握手,HTTP 可以在第一个 Round trip 就发送数据。注:本文翻译自 Mark Nottingham 的博客,Mark 是 IETF HTTP Working Group 的主席,Akaimai 公司的首席架构师。原标题:Ideal HTTP Performance